Skip to content

chat: box only your own messages, and make the cursor findable - #102

Merged
hbrooks merged 3 commits into
mainfrom
chat/density-and-cursor
Aug 6, 2026
Merged

chat: box only your own messages, and make the cursor findable#102
hbrooks merged 3 commits into
mainfrom
chat/density-and-cursor

Conversation

@hbrooks

@hbrooks hbrooks commented Aug 6, 2026

Copy link
Copy Markdown
Member

Important

Changes chat display to reduce visual clutter and clarify message ownership: only your messages get the lifted panel box; agent output stays on the canvas.

  • Messages you sent sit on the elevated panel (like the composer); agent prose, tool output, and notices are bare canvas, creating denser display.
  • Selection is now carried solely by the cyan cursor marker in the gutter; the full-row highlight is removed. All rows of the selected block remain navigable with hints.
  • Tool runs no longer nest under your messages (they read as work you did); only ASSISTANT messages and thinking blocks can have nested tool activity. A turn-opening tool call stays flat.
  • Opening a message with replaces the collapsed "Ran N tool calls" fold with the actual calls it stood for, instead of keeping a stale count above them.
  • Metadata formatting: durations and token counts now read as comma-separated prose ("23s, 4 tokens") instead of parenthesized asides ("(23s) · ↓ 4 tokens").
  • Branch folds () now get extra padding before their text to prevent the body from reading as touching the mark.
  • Added cursor: '#5fd3e0' (cyan) to theme as the sole color for selection; emphasis is now carried by hue (brightness alone was too quiet on busy frames).

This description was created by Ellipsis for 540d640. It will automatically update as commits are pushed.

The tint and pad on every message turned the transcript into a stack of
stripes with no room to read. Only what you sent is a lifted box now;
everything the agent says or does sits bare on the canvas.

- metadata reads as prose ("23s, 4 tokens", not "(23s - 4 tokens)")
- the selection highlight is gone; the cyan ▶ carries "you are here"
- a turn-opening tool run no longer branches off YOUR message, where it
  read as work you did
- opening a message replaces its "Ran N ..." fold with the calls it stood
  for, instead of leaving a stale count above them
- ⎿ gets a column of breathing room before its text

@ellipsis-dev ellipsis-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

4 issues detected

Reviewed f30a92a in 8 minutes, 54 seconds.
  • Reviewed 1 commit with 584 lines of code in 6 files
  • Ran 1 review agent producing 4 comments where 4 were posted
  • This pipeline runs no gatekeeper, so findings are posted as written.
  • View full details on ellipsis.dev

This review was created by Ellipsis. You can tag @ellipsis in this pull request.

Comment thread src/ui/transcriptRows.ts Outdated
if (!isToolActivity(item)) {
out.push({ item, indent: 0, nested: false, attach: false })
parent = item.key
parent = item.kind === 'assistant' ? item.key : null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A turn-opening tool run becomes unreachable: its fold is a flat nav stop that → cannot open, so the calls and their output can only be seen with ctrl+r.

For [user, tool, tool_result] (the agent starting a turn with a tool call — the common Claude Code shape), collapseToolRuns yields [user, grp:tool]. With parent now null after a user message, the fold gets navKey undefined, so it is its own ↑/↓ stop. In the → handler (ConnectApp.tsx:1141-1144) hasToolRun('grp:t1') is false (no row names it as its block) and isCollapsible(notice) is false, so → is inert on it; ← is inert too (no parentKey). On main the same fold carried navKey 'u', so → on your message revealed the run. Verified against both revisions.

Comment thread src/ui/ConnectApp.tsx
out.push(item)
if (!isToolActivity(item)) {
out.push(item)
parent = item.key

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visible still treats any non-tool item as a fold's parent while layOutItems restricts parenthood to assistant messages, so opening a long user message silently unfolds the tool run below it.

A user turn over COLLAPSE_LINES is collapsible, so → adds its key to openedKeys (ConnectApp.tsx:1142) just to un-clamp the text. Here parent is that user key, so the following grp: fold is replaced by foldRun(...) — the "Ran N shell commands" line vanishes and the raw ●/⎿ lines appear at indent 0 (layOutItems keeps them flat and un-nested, since its own parent is null). Confirmed by replaying this memo over [user(long), tool, tool_result] with openedKeys={'u'}.

Suggested change
parent = item.key
parent = item.kind === 'assistant' ? item.key : null

Comment thread src/ui/transcriptRows.ts Outdated
// ("Ran 2 tool calls") takes the branch glyph — as a notice it would
// otherwise wear ✦, the mark for the infrastructure speaking, which is not
// what a fold is.
const gutter = opts.nested && item.kind === 'notice' ? BRANCH_GLYPH : gutterFor(item)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A flat (turn-opening) fold now wears ✦ instead of ⎿, the exact case the comment two lines above says must not happen; key the glyph on the fold itself, not on nested.

Turn-opening runs are no longer nested, so opts.nested is false for their fold and gutterFor(notice) returns '✦' — the infrastructure mark. Ran itemRows over collapseToolRuns([user, tool, tool_result, assistant, tool, tool_result]): the first fold renders '✦ Ran 1 shell command', the second '⎿ Ran 1 shell command'.

Suggested change
const gutter = opts.nested && item.kind === 'notice' ? BRANCH_GLYPH : gutterFor(item)
const gutter = item.key.startsWith('grp:') ? BRANCH_GLYPH : gutterFor(item)

Comment thread src/ui/ConnectApp.tsx
: row.panel
? SURFACE_ELEVATED
: undefined
const background = row.panel ? SURFACE_ELEVATED : undefined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the tint gone, a block taller than the chat window shows no cursor at all: the only row that can carry ▶ is scrolled off-frame.

markerRowId is the block's FIRST row with a gutter, and ↑ onto a too-tall entry calls ensureVisible(key, -1) → snapToEntry bottom-aligns it, so that first row sits above view.start. Assistant prose isn't collapsible, so there is no "+N lines (→ to expand)" hint on screen either — nothing on the frame indicates which block is selected until several more ↑ presses scroll its top back into view.

Separating "which message does this run BELONG to" from "does it indent
under that message" is the root fix for the first three: I had collapsed
both onto one variable, so refusing to indent under your message also
took away the run's owner.

- a turn-opening run belongs to your message again, so → opens it and ←
  steps back out; it just doesn't branch off it visually
- opening a long user message no longer unfolds the run below it as a
  side effect of un-clamping the text
- a flat fold still wears ⎿, not the ✦ infrastructure mark
- the ▶ falls to the topmost VISIBLE row of the selected block: with no
  highlight bar left, a block taller than the window showed no cursor

@ellipsis-dev ellipsis-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Changes requested ❌ — 1 issue

Incrementally reviewed 96b6463 in 10 minutes, 15 seconds.
  • Reviewed 1 commit with 114 lines of code in 3 files
  • Ran 1 review agent producing 1 comment where 1 was posted
  • This pipeline runs no gatekeeper, so findings are posted as written.
  • View full details on ellipsis.dev

This review was created by Ellipsis. You can tag @ellipsis in this pull request.

Comment thread src/ui/transcriptRows.ts Outdated
if (!isToolActivity(item)) {
out.push({ item, indent: 0, nested: false, attach: false })
parent = item.key
parentIsAgent = item.kind === 'assistant'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parentIsAgent is false for thinking items, so a tool run that follows a thinking block never nests — with extended thinking on that is nearly every run, and the ⎿ hierarchy this PR is built around disappears.

Claude Code's usual turn shape is thinking → tool_use → tool_result (eventToItems emits kind: 'thinking' for every thinking block). layOutItems([think('th'), fold('t1')]) returns {indent: 0, nested: false, attach: false} for the fold, where main returned {indent: 2, nested: true, attach: true}. The stated reason for staying flat is that a ⎿ branch under YOUR lifted box reads as work you did — that does not apply to a ✻ thinking line, which is the agent. Note the live-tail counterpart at ConnectApp.tsx:816 (nested: said?.kind === 'assistant') needs the same predicate, or the running ✻ line will sit flat and then jump right one level when its ⎿ result lands.

Suggested change
parentIsAgent = item.kind === 'assistant'
parentIsAgent = item.kind === 'assistant' || item.kind === 'thinking'

With extended thinking on, thinking -> tool_use -> tool_result is the
usual turn shape, so treating thinking as not-the-agent flattened almost
every run and took the hierarchy with it. Both the committed rows and the
live activity line now share one isAgentSpeech predicate; when they
disagreed, the running line sat flat and jumped a level as its result
landed.

@ellipsis-dev ellipsis-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Incrementally reviewed 540d640 in 4 minutes.
  • Reviewed 1 commit with 77 lines of code in 3 files
  • Ran 1 review agent producing 0 comments where 0 were posted
  • This pipeline runs no gatekeeper, so findings are posted as written.
  • View full details on ellipsis.dev

This review was created by Ellipsis. You can tag @ellipsis in this pull request.

@hbrooks
hbrooks merged commit 6e04125 into main Aug 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant